//
//  CSudoku.m
//  iSudoku
//
//  Created by acumen on 5/23/13.
//  Copyright (c) 2013 Tri Vo. All rights reserved.
//

#import "CSudoku.h"
#import "CLevel.h"

static CSudoku *instance = nil;

@implementation CSudoku

+ (id)getInstance {
    if (!instance) {
        instance = [[CSudoku alloc] init];
    }
    return instance;
}

- (id)init {
    self = [super init];
    if (self) {
        [self initMatrixs];
        [self createSubRegions];
        if ([self solve]) {
            [self showSolve];
        }
        
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                NSNumber *temp = [mMatrix_Sudoku objectAtRow:i column:j withTypeClass:[NSNumber class]];
                [mMatrix_Result insertObject:temp atRow:i column:j];
            }
        }
        [self merge];
    }
    return self;
}

- (void)dealloc {
    [mMatrix_Empty release];
    [mMatrix_Result release];
    [mMatrix_SubSquare release];
    [mMatrix_Sudoku release];
    [super dealloc];
}

#pragma mark - Init Matrixs

- (void) initMatrixs {
    [self initSubquare];
    [self initEmpty];
    [self initResult];
    [self initSudoku];
    mMatrixTemp = [[CRL2DArray alloc] initWithRows:9 columns:9];
}

- (void) initSudoku {
    mMatrix_Sudoku = [[CRL2DArray alloc] initWithRows:9 columns:9];
}

- (void) initEmpty {
    mMatrix_Empty = [[CRL2DArray alloc] initWithRows:9 columns:9];
    [mMatrix_Empty insertAllObjects:[NSNumber numberWithInt:0]];
}

- (void) initResult {
    mMatrix_Result = [[CRL2DArray alloc] initWithRows:9 columns:9];
}

- (void) initSubquare {
    mMatrix_SubSquare = [[CRL2DArray alloc] initWithRows:9 columns:9];
    for (int i = 0; i < 9; i++) {
        for (int j = 0; j < 9; j++) {
            
            // Sub-square 0
            NSNumber *tempNumber;
            if (i < 3 && j <3) {
                tempNumber = [NSNumber numberWithInt:0];
            }
            
            // Sub-square 1
            if (j >= 3 && j < 6 &&  i < 3) {
                tempNumber = [NSNumber numberWithInt:1];
            }
            
            // Sub-square 2
            if (j >= 6 && i < 3) {
                tempNumber = [NSNumber numberWithInt:2];
            }
            
            // Sub-quare 3
            if (j < 3 && i >= 3 && i < 6) {
                tempNumber = [NSNumber numberWithInt:3];
            }
            
            // Sub-square 4
            if (i >= 3 && i < 6 && j >= 3 && j <6) {
                tempNumber = [NSNumber numberWithInt:4];
            }
            
            // Sub-square 5
            if (i >= 3 && i < 6 && j>=6) {
                tempNumber = [NSNumber numberWithInt:5];
            }
            
            // Sub-square 6
            if (i>= 6 && j <3) {
                tempNumber = [NSNumber numberWithInt:6];
            }
            
            // Sub-square 7
            if (i >= 6 && j >= 3 && j < 6) {
                tempNumber = [NSNumber numberWithInt:7];
            }
            
            // Sub-square 8
            if (i >= 6 && j >= 6) {
                tempNumber = [NSNumber numberWithInt:8];
            }
            [mMatrix_SubSquare insertObject:tempNumber atRow:i column:j];
        }
    }
    /*
     {0,0,0,1,1,1,2,2,2},
     {0,0,0,1,1,1,2,2,2},
     {0,0,0,1,1,1,2,2,2},
     {3,3,3,4,4,4,5,5,5},
     {3,3,3,4,4,4,5,5,5},
     {3,3,3,4,4,4,5,5,5},
     {6,6,6,7,7,7,8,8,8},
     {6,6,6,7,7,7,8,8,8},
     {6,6,6,7,7,7,8,8,8}
     */
}

#pragma mark - Class Methods

- (BOOL) flagCheckMap {
    return NO;
}

- (BOOL) flagCheckResult {
    for (int i = 0; i < 9; i++) {
        for (int j = 0; j < 9; j++) {
            if ([[mMatrix_Sudoku objectAtRow:i column:j withTypeClass:[NSNumber class]] intValue] == 0) {
                return NO;
            }
        }
    }
    
    for (int i = 0; i < 9; i++) {
        for (int j = 0; j < 9; j++) {
            if ([[mMatrix_Sudoku objectAtRow:i column:j withTypeClass:[NSNumber class]] intValue] != 0) {
                for (int l = 0; l < 9; l++) {
                    if ((l != j) && [self compareTwoNumber:CGPointMake(i, l) andNumber2:CGPointMake(i, j) withArray:mMatrix_Sudoku]) {
                         // Wrong Row
                        return NO;
                    }
                    
                    if ((l != j) && [self compareTwoNumber:CGPointMake(l, j) andNumber2:CGPointMake(i, j) withArray:mMatrix_Sudoku]) {
                        // Wrong column
                        return NO;
                    }
                }
                
                int squareIndex = [[mMatrix_SubSquare objectAtRow:i column:j withTypeClass:[NSNumber class]] intValue];
                for (int x = 0; x < 9; x++) {
                    for (int y = 0; y < 9; y++) {
                        if ([self compareNumber:CGPointMake(x, y) withIntValue:squareIndex inArray:mMatrix_SubSquare]) {
                            if ((x != i) || ((y != j) && ([self compareTwoNumber:CGPointMake(x, y) andNumber2:CGPointMake(i, j) withArray:mMatrix_Sudoku]))) {
                                // Wrong region
                                return NO;
                            }
                        }
                    }
                }
                
            }
        }
    }
    return YES;
}

- (BOOL) compareTwoNumber:(CGPoint )point andNumber2:(CGPoint)point2 withArray:(CRL2DArray *)array {
    NSNumber *tempA = [array objectAtRow:point.x column:point.y withTypeClass:[NSNumber class]];
    NSNumber *tempB = [array objectAtRow:point2.x column:point2.y withTypeClass:[NSNumber class]];
    if ([tempA isEqualToNumber:tempB]) {
        return YES;
    }
    
    return NO;
}

- (BOOL) compareNumber:(CGPoint )point withIntValue:(int)iVal inArray:(CRL2DArray *)array {
    NSNumber *tempA = [array objectAtRow:point.x column:point.y withTypeClass:[NSNumber class]];
    if ([tempA intValue] == iVal) {
        return YES;
    }
    return NO;
}

- (int) getCase:(int) number {
    switch (number) {
        case 0:
        case 1:
        case 2:
        {
            return 0;
        }
            break;
        case 3:
        case 4:
        case 5:
        {
            return 3;
        }
            break;
        case 6:
        case 7:
        case 8:
        {
            return 6;
        }
            break;
        default:
        {
            return 0;
        }
            break;
    }
}

- (void) generateRegionWithStartX:(int)xStart toEndX:(int)xEnd andFromStartY:(int)yStart toEndY:(int)yEnd {
    int x = arc4random()*(xEnd - xStart) + xStart;
    int y = arc4random()*(yEnd - yStart) + yStart;
    int no = arc4random()*9 + 1;
    [mMatrix_Sudoku insertObject:[NSNumber numberWithInt:no] atRow:x column:y];
}

- (void) createSubRegions {
    int x = 0;
    int y = 0;
    for (int i = 0; i < 9; i++) {
        for (int j = 0; j < 9; j++) {
            // Get case for x
            x = [self getCase:i];
            // Get case for y
            x = [ self getCase:j];            
            if ((i == 3 && j == 3) || (i == 0 && j == 0) || (i == 6 && j == 6)) {
                if (i == 0 && j == 0) {
                    i = 3;
                    j = 3;
                }
                if (i == 3 && j == 3) {
                    j = 6;
                    i = 6;
                }
                [self generateRegionWithStartX:x toEndX:(x+3) andFromStartY:y toEndY:(y+3)];
            }
        }
    }
}

- (void) merge {
    for (int i = 0; i < 9; i++) {
        for (int j = 0; j < 9; j++) {
            NSNumber *temp = [[[CLevel getInstance] getCurrentMatrix] objectAtRow:i column:j withTypeClass:[NSNumber class]];
            if ([temp intValue] == 0) {
                [mMatrix_Sudoku insertObject:[NSNumber numberWithInt:0] atRow:i column:j];
            }
        }
    }
}

- (BOOL) solve {
    int a = 0;
    int b = 0;
    int allElements = 10;
    NSMutableArray *tempArr = nil;
    if (![self flagCheckMap]) {
        return NO;
    }
    
    for (int i = 0; i < 9; i++) {
        for (int j = 0; j < 9; j++) {
            // browse empty cells or discordant cells
            NSNumber *temp = [mMatrixTemp objectAtRow:i column:j withTypeClass:[NSNumber class]];
            if ([temp intValue] == 0) {
                NSMutableArray *M = [[NSMutableArray alloc] init];
                for (int i = 0; i < 10; i++) {
                    [M addObject:[NSNumber numberWithInt:i]];
                }
                // Remove M's cells that match with m_v3's cells
                for (int k = 0; k < 9; k++) {
                    NSNumber *tempNo = [mMatrixTemp objectAtRow:i column:k withTypeClass:[NSNumber class]];
                    [M replaceObjectAtIndex:[tempNo intValue] withObject:[NSNumber numberWithInt:0]];
                }
                for (int l = 0; l < 9; l++) {
                    NSNumber *tempNo = [mMatrixTemp objectAtRow:l column:j withTypeClass:[NSNumber class]];
                    [M replaceObjectAtIndex:[tempNo intValue] withObject:[NSNumber numberWithInt:0]];
                }
                // Delete M's cell that matchs with m_v3's cell's position
                [self checkField:M withX:i andY:j];
                // count unused elements
                int unusedElements = 0;
                for (int h = 1; h < 10; h++) {
                    NSNumber *tempNo = [M objectAtIndex:h];
                    if ([tempNo intValue] != 0) {
                        unusedElements++;
                    }
                }
                // check other cases if there is one of them suit
                if (unusedElements < allElements) {
                    allElements = unusedElements;
                    tempArr = M;
                    a = i;
                    b = j;
                }
            }
        }
    }
    // if there are no numbers in m_v3  = 0
    if (allElements == 10) {
        return YES;
    }
    // Do not have elements to use
    if (allElements == 0) {
        return NO;
    }
    // Try other solutions to find the most suitable one
    for (int n = 1; n < 10; n++) {
        NSNumber *tempNo = [tempArr objectAtIndex:n];
        if ([tempNo intValue] != 0) {
            [mMatrixTemp insertObject:tempNo atRow:a column:b];
            if ([self solve]) {
                return YES;
            }
        }
    }
    // Remove position if not suitable
    [mMatrixTemp insertObject:[NSNumber numberWithInt:0] atRow:a column:b];
    return NO;
}

- (void) showSolve {
    for (int i = 0; i < 9; i++) {
        for (int j = 0; j < 9; j++) {
            NSNumber *temp = [mMatrixTemp objectAtRow:i column:j withTypeClass:[NSNumber class]];
            [mMatrix_Sudoku insertObject:temp atRow:i column:j];
        }
    }
}

- (CRL2DArray *)getCurrentMatrix {
    return mMatrix_Sudoku;
}

- (CRL2DArray *)getMatrixResult {
    return mMatrix_Result;
}

- (void) checkField:(NSMutableArray *)arr withX:(int)i andY:(int)j {
    NSNumber *subquareIndex = [mMatrix_SubSquare objectAtRow:i column:j withTypeClass:[NSNumber class]];
    for (int x = 0; x < 9; x++) {
        for (int y = 0; y < 9; y++) {
            NSNumber *temp = [mMatrix_SubSquare objectAtRow:x column:y withTypeClass:[NSNumber class]];
            if ([temp isEqualToNumber:subquareIndex]) {
                NSNumber *tempNp = [mMatrixTemp objectAtRow:x column:y withTypeClass:[NSNumber class]];
                [arr replaceObjectAtIndex:[tempNp intValue] withObject:[NSNumber numberWithInt:0]];
            }
        }
    }
}

@end

